/** @file tex_bmp.cpp
    @brief BMP images */

#include "tex/tex_bmp.h"

using namespace G3;

// TODO:: Test
// TODO:: Endianness & portability
// TODO:: Check for memory leaks
// TODO:: Optimize

//	BUG:: media/notex.bmp
//		 	ERROR! LoadBMP: Unable to read pixel (34 13)..
//			WARNING! FILESTREAM::Seek: Offset (140326740) too big (TotalSize: 4312)..

boost::shared_ptr<G3::BITMAP> G3::LoadBMP (STREAM *Stream, LOG *Log) {
	byte FileHeader [15];
	byte InfoHeader [41];
	byte Palette [768];
	byte Blank = 0;
	COL3B Color;
	int NumColors = 0;
	int HStart = 0;
	int HEnd = 0;
	int HDir = 0;
	int ScanWidth = 0;
	int CurPixel = 0;
	byte Group = 0;
	int Dest = 0;
	int Src = 0;
	byte Pixel8 = 0;
	ushort Pixel16 = 0;
	int Padding = 0;
	int Pad = 0;
	long Size = 0;
	long Start = 0;
	int i = 0, j = 0, k = 0;

	Assert (Stream, "LoadBMP: Stream argument is a NULL pointer");

	boost::shared_ptr<BITMAP> Bitmap (new BITMAP);

	if (Bitmap == NULL) {
		if (Log)
			Log->Report ("ERROR! LoadBMP: Not enough memory for a BITMAP class (at least %d more bytes needed)..\n", sizeof (BITMAP));
		return boost::shared_ptr<BITMAP>();
	}

	Start = Stream->Tell (STREAM::STREAM_GET);
	Size = Stream->Tell (STREAM::STREAM_END) - 1;

	if (!Stream->Read (FileHeader, 14) ||
		!Stream->Read (InfoHeader, 40)) {
			if (Log)
				Log->Report ("ERROR! LoadBMP: This file has corrupt headers..\n");
			Stream->Seek (Size, STREAM::STREAM_SET);
			return boost::shared_ptr<BITMAP>();
	}

	// bfType
	if (((word *) FileHeader)[0] != BITMAP_ID) {
		if (Log)
			Log->Report ("ERROR! LoadBMP: Not a bitmap file..\n");
		Stream->Seek (Size, STREAM::STREAM_SET);
		return boost::shared_ptr<BITMAP>();
	}

	// biCompression
	if (((dword *) InfoHeader)[4] != BI_RGB) {
		if (Log)
			Log->Report ("WARNING! LoadBMP: We do not support compressed bitmaps yet..\n");
		Stream->Seek (Size, STREAM::STREAM_SET);
		return boost::shared_ptr<BITMAP>();
	}

	// biBitCount
	if (((word *) InfoHeader)[7] <= 8) {
		NumColors = 1 << ((word *) InfoHeader)[7];

		// Palette information in bitmaps is stored in BGR_ format.
		for (i=0; i<NumColors * 3; i+=3) {
			if (!Stream->Read (&Color.b, 1) ||
				!Stream->Read (&Color.g, 1) ||
				!Stream->Read (&Color.r, 1) ||
				!Stream->Read (&Blank, 1)) {

					if (Log)
						Log->Report ("ERROR! LoadBMP: Invalid palette..\n");
					Stream->Seek (Size, STREAM::STREAM_SET);
					return boost::shared_ptr<BITMAP>();
				}

			Palette [i    ] = Color.r;
			Palette [i + 1] = Color.g;
			Palette [i + 2] = Color.b;
		}
	}

	// biWidth
	Bitmap->Width = ((long *) InfoHeader)[1];

	// biHeight
	if (((long *) InfoHeader)[2] < 0) {
		Bitmap->Height = -((long *) InfoHeader)[2];
		HStart = 0;
		HEnd = Bitmap->Height;
		HDir = 1;
	} else {
		Bitmap->Height = ((long *) InfoHeader)[2];
		HStart = Bitmap->Height - 1;
		HEnd = -1;
		HDir = -1;
	}

	if (((word *) InfoHeader)[7] == 32) {
		Bitmap->Size = Bitmap->Width * Bitmap->Height * 4;
		Bitmap->Data = new byte [Bitmap->Size];
	} else {
		Bitmap->Size = Bitmap->Width * Bitmap->Height * 3;
		Bitmap->Data = new byte [Bitmap->Size];
	}

	// biBitCount
	if (((word *) InfoHeader)[7] == 1) {
		// For bitmaps, each scanline is dword-aligned.
		ScanWidth = (Bitmap->Width + 7) >> 3;
		if (ScanWidth & 3)
			ScanWidth += 4 - (ScanWidth & 3);

		for (i=HStart; i!=HEnd; i+=HDir) {
			CurPixel = 0;

			for (j=0; j<ScanWidth; j++) {
				if (!Stream->Read (&Group, 1)) {
					if (Log)
						Log->Report ("ERROR! LoadBMP: Unable to read pixel group (%d %d)..\n", j, i);
					Bitmap->Destroy ();
					Stream->Seek (Size, STREAM::STREAM_SET);
					return boost::shared_ptr<BITMAP>();
				}

				for (k=7; k>=0; k--) {
					if (CurPixel < Bitmap->Width) {
						Dest = 3 * (i * Bitmap->Width + CurPixel);
						Src = 3 * ((Group & (1 << k)) >> k);

						Bitmap->Data [Dest    ] = Palette [Src    ];
						Bitmap->Data [Dest + 1] = Palette [Src + 1];
						Bitmap->Data [Dest + 2] = Palette [Src + 2];

						CurPixel++;
					}
				}
			}
		}
		Bitmap->BitsPerPixel = 24;
	} else if (((word *) InfoHeader)[7] == 4) {
		ScanWidth = (Bitmap->Width + 1) >> 1;
		if (ScanWidth & 3)
			ScanWidth += 4 - (ScanWidth & 3);

		for (i=HStart; i!=HEnd; i+=HDir) {
			CurPixel = 0;

			for (j=0; j<ScanWidth; j++) {
				if (!Stream->Read (&Group, 1)) {
					if (Log)
						Log->Report ("ERROR! LoadBMP: Unable to read pixel group (%d %d)..\n", j, i);
					Bitmap->Destroy ();
					Stream->Seek (Size, STREAM::STREAM_SET);
					return boost::shared_ptr<BITMAP>();
				}

				for (k=4; k>=0; k-=4) {
					if (CurPixel < Bitmap->Width) {
						Dest = 3 * (i * Bitmap->Width + CurPixel);
						Src = 3 * ((Group & 0xF0) >> k);

						Bitmap->Data [Dest    ] = Palette [Src    ];
						Bitmap->Data [Dest + 1] = Palette [Src + 1];
						Bitmap->Data [Dest + 2] = Palette [Src + 2];

						CurPixel++;
					}
				}
			}
		}
		Bitmap->BitsPerPixel = 24;
	} else if (((word *) InfoHeader)[7] == 8) {
		ScanWidth = Bitmap->Width;
		if (ScanWidth & 3)
			ScanWidth += 4 - (ScanWidth & 3);

		for (i=HStart; i!=HEnd; i+=HDir) {
			CurPixel = 0;

			for (j=0; j<ScanWidth; j++) {
				if (!Stream->Read (&Pixel8, 1)) {
					if (Log)
						Log->Report ("ERROR! LoadBMP: Unable to read pixel (%d %d)..\n", j, i);
					Bitmap->Destroy ();
					Stream->Seek (Size, STREAM::STREAM_SET);
					return boost::shared_ptr<BITMAP>();
				}

				if (CurPixel < Bitmap->Width) {
					Dest = 3 * (i * Bitmap->Width + CurPixel);
					Src = 3 * Pixel8;

					Bitmap->Data [Dest    ] = Palette [Src    ];
					Bitmap->Data [Dest + 1] = Palette [Src + 1];
					Bitmap->Data [Dest + 2] = Palette [Src + 2];

					CurPixel++;
				}
			}
		}
		Bitmap->BitsPerPixel = 24;
	} else if (((word *) InfoHeader)[7] == 16) {
		ScanWidth = Bitmap->Width << 1;

		if (ScanWidth & 3)
			Padding = 4 - (ScanWidth & 3);
		else
			Padding = 0;

		for (i=HStart; i!=HEnd; i+=HDir) {
			for (j=0; j<Bitmap->Width; j++) {
				if (!Stream->Read (((byte *) &Pixel16), 2)) {
					if (Log)
						Log->Report ("ERROR! LoadBMP: Unable to read pixel (%d %d)..\n", j, i);
					Bitmap->Destroy ();
					Stream->Seek (Size, STREAM::STREAM_SET);
					return boost::shared_ptr<BITMAP>();
				}

				// Big, Little Endian
				Color.r = ((Pixel16 >> 10) & 0x1F) << 3;
				Color.g = ((Pixel16 >> 5) & 0x1F) << 3;
				Color.b = (Pixel16 & 0x1F) << 3;

				Dest = (i * Bitmap->Width + j) << 1;

				Bitmap->Data [Dest    ] = Color.r;
				Bitmap->Data [Dest + 1] = Color.g;
				Bitmap->Data [Dest + 2] = Color.b;

				CurPixel++;
			}
			if (Padding)
				Stream->Read (((byte *) &Pad), Padding);
		}
		Bitmap->BitsPerPixel = 24;
	} else if (((word *) InfoHeader)[7] == 24) {
		ScanWidth = Bitmap->Width * 3;

		if (ScanWidth & 3)
			Padding = 4 - (ScanWidth & 3);
		else
			Padding = 0;

		for (i=HStart; i!=HEnd; i+=HDir) {
			for (j=0; j<Bitmap->Width; j++) {
				if (!Stream->Read (&Color.b, 1) ||
					!Stream->Read (&Color.g, 1) ||
					!Stream->Read (&Color.r, 1)) {
						if (Log)
							Log->Report ("ERROR! LoadBMP: Unable to read pixel (%d %d)..\n", j, i);
						Bitmap->Destroy ();
						Stream->Seek (Size, STREAM::STREAM_SET);
						return boost::shared_ptr<BITMAP>();
					}

				Dest = 3 * (i * Bitmap->Width + j);

				Bitmap->Data [Dest    ] = Color.r;
				Bitmap->Data [Dest + 1] = Color.g;
				Bitmap->Data [Dest + 2] = Color.b;
			}
			if (Padding)
				Stream->Read (((byte *) &Pad), Padding);
		}
		Bitmap->BitsPerPixel = 24;
	} else if (((word *) InfoHeader)[7] == 32) {
		Bitmap->Flags |= BITMAP_RGBA;
		for (i=HStart; i!=HEnd; i+=HDir) {
			for (j=0; j<Bitmap->Width; j++) {
				if (!Stream->Read (&Blank, 1) ||
					!Stream->Read (&Color.b, 1) ||
					!Stream->Read (&Color.g, 1) ||
					!Stream->Read (&Color.r, 1)) {
						if (Log)
							Log->Report ("ERROR! LoadBMP: Unable to read pixel (%d %d)..\n", j, i);
						Bitmap->Destroy ();
						Stream->Seek (Size, STREAM::STREAM_SET);
						return boost::shared_ptr<BITMAP>();
					}

				Dest = 4 * (i * Bitmap->Width + j);

				Bitmap->Data [Dest    ] = Color.r;
				Bitmap->Data [Dest + 1] = Color.g;
				Bitmap->Data [Dest + 2] = Color.b;
				Bitmap->Data [Dest + 3] = Blank;

				CurPixel++;
			}
			if (Padding)
				Stream->Read (((byte *) &Pad), Padding);
		}
		Bitmap->BitsPerPixel = 24;
	} else {
		if (Log)
			Log->Report ("ERROR! LoadBMP: Not a valid bit depth (%d)..\n", ((word *) InfoHeader)[7]);
		Bitmap->Destroy ();
		Stream->Seek (Size, STREAM::STREAM_SET);
		return boost::shared_ptr<BITMAP>();
	}

	// Mark the start of the next file
	Stream->Seek (Size, STREAM::STREAM_SET);
	Stream->Flush ();
	//Stream->Flush (true);
	//Stream->SetFileStart ();

	return Bitmap;
}
